﻿// Necroz project team © <2015> <ArchLord2EMU>
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
// 
// Author:Sagara
// Date:2015-09-06-23:29

using System;
using System.Linq;
using ArchLord2Commons.Network.Cryptography;
using CM.Framework.Network;
using NLog;

namespace ArchLord2Commons.Network
{
    public abstract class AALClient : ATcpClient
    {
        private static readonly Logger Log = LogManager.GetCurrentClassLogger();

        internal IAALFrameHandler ClientFrameHandler;

        private Session Cryptor;

        internal void OnClientConnected(IAALFrameHandler clientFrameHandler)
        {
            ClientFrameHandler = clientFrameHandler;

            Cryptor = new Session();

            ReceiveRecursion();

            OnConnected();
        }

        /// <summary>
        /// Send network frame to <code>this</code> client
        /// </summary>
        /// <param name="netFrame"></param>
        /// <param name="useCheck"></param>
        /// <param name="opc">frame opcode</param>
        public void Send(AALNetFrame netFrame, byte useCheck = 0, ushort? opc = null)
        {
            var raw = new byte[5 + netFrame.Length];

            var len = BitConverter.GetBytes((ushort)(netFrame.Length + 5));

            Buffer.BlockCopy(len, 0, raw, 0, 2);

            raw[2] = useCheck;

            var opcode = BitConverter.GetBytes(opc ?? ClientFrameHandler.GetOpcode(netFrame.GetType()));

            Buffer.BlockCopy(opcode, 0, raw, 3, 2);
            Buffer.BlockCopy(netFrame.Payload, 0, raw, 5, netFrame.Length);

            if (useCheck != 0)
            {
                int cryptedBodylen = raw.Length - 3;

                var cryptedBuffer = new byte[cryptedBodylen];

                Buffer.BlockCopy(raw, 3, cryptedBuffer, 0, cryptedBodylen);
         
                Cryptor.Encrypt(ref cryptedBuffer);

                Buffer.BlockCopy(cryptedBuffer, 0, raw, 3, cryptedBodylen);
            }

            ASend(raw);
        }

        /// <summary>
        /// Ayncroniously receive game frames
        /// Recursive method
        /// </summary>
        protected void ReceiveRecursion()
        {
            var snippet = new byte[3];

            AReceive(snippet, delegate
            {
                var len = BitConverter.ToUInt16(snippet, 0);
                var state = snippet[2];
                var bodyLen = (ushort)(len - 3);

                AALNetFrame frame;

                if (bodyLen > 0)
                {
                    byte[] bodyData = new byte[bodyLen];

                    AReceive(bodyData, delegate
                    {
                        if (state == 1)
                            Cryptor.Decrypt(ref bodyData);

                        ushort opCode = BitConverter.ToUInt16(bodyData, 0);
                        byte[] payload = bodyData.Skip(2).ToArray();

                        frame = ClientFrameHandler.GetFrameInstance(opCode);

                        frame.Opcode = opCode;
                        frame.Payload = payload;

                        try
                        {
                            FrameReceived(frame);
                        }
                        catch (Exception e)
                        {
                            Log.ErrorException("Exception occured till process received frame! WARNING! This exception cause force client diconnetion in release build!", e);
#if !DEBUG
                        Log.ErrorException("Exception occured till process received frame. Client will be disconnected!", e);
                        ADisconnect(false);
                        return;
#endif
                        }
                        ReceiveRecursion();
                    });
                }
                else
                {
                    FrameReceived(null);
                    ReceiveRecursion();
                }
            });
        }

        /// <summary>
        /// Called when <code>this</code> client connected and initialized
        /// </summary>
        protected abstract void OnConnected();

        /// <summary>
        /// Called when new game frame received from <code>this</code> client
        /// </summary>
        /// <param name="frame"></param>
        protected abstract void FrameReceived(AALNetFrame frame);
    }
}
